/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.bth.ke.firstfriend.depend;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import se.bth.ke.firstfriend.model.Article;
import se.bth.ke.firstfriend.model.User;

/**
 *
 * @author nico.rehwaldt
 */
@Component
public class DataMiner {
    
    // 0 <- 2 1  (70.0, 85.7)
    private static final Pattern ASSOCIATION_RULE_PATTERN = 
        Pattern.compile("([\\d]+) <- ((?:[\\d]+ )+) \\(([\\d.]+), ([\\d.]+)\\)");
    private static final Pattern BASE_PATTERN =
        Pattern.compile("([\\d]+) ");

    private static final String FREQUENT_SET_OPTION = "-ts";
    private static final String ASSOCIATION_RULE_OPTION = "-tr";
    
    private static final Logger logger;
    
    static {
        logger = Logger.getLogger(DataMiner.class.getName());
        logger.setLevel(Level.ALL);
    }
    
    @Value("${aprioriExecutor.executable}")
    private String APRIORI_EXECUTABLE;
    
    @PersistenceContext
    private EntityManager em;
    
    @Transactional
    public Set<AssociationRule> mineAssocitationRules(Map<String, String> hints) {
        
        if (!hints.containsKey("-s")) {
            hints.put("-s", "30");
        }
        
        if (!hints.containsKey("-m")) {
            hints.put("-m", "1");
        }
        
        hints.put(ASSOCIATION_RULE_OPTION, "");
        
        try {
            File transactionFile = File.createTempFile("transactions", null);
            File outFile = File.createTempFile("transactions_result", null);
            
            try {
                saveTransactions(transactionFile);
                int exitCode = new AprioriExecutable(APRIORI_EXECUTABLE)
                                        .read(transactionFile)
                                        .write(outFile)
                                        .logTo(logger, Level.FINE)
                                        .executeWithArgs(hints);
                

                if (exitCode != 0) {
                    throw new IOException("Unexpected return value of subprocess " + exitCode);
                }
                
                return parseAssociationRules(outFile);
            } catch (IOException e) {
                logger.warning("Failed to extract frequent item sets: " + e);
                throw new RuntimeException(e);
            } finally {
//                transactionFile.delete();
//                outFile.delete();
            }
        } catch (IOException e) {
            logger.severe("Failure to finalize when extracting frequent item sets: " + e);
            throw new RuntimeException(e);
        }
    }
    
    public Set<AssociationRule> parseAssociationRules(File outFile) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(outFile));
        Set<AssociationRule> associationRules = new HashSet();
        
        while (true) {
            String line = reader.readLine();
            if (line == null) {
                break;
            }
            
            Matcher matcher = ASSOCIATION_RULE_PATTERN.matcher(line);
            
            if (!matcher.matches()) {
                throw new IOException("Unexpected line of input: " + line);
            }
            
            MatchResult result = matcher.toMatchResult();
            long associationResult = Long.parseLong(result.group(1));
            double associationAccuracy = Double.parseDouble(result.group(4));
            double baseAccuracy = Double.parseDouble(result.group(3));
            
            Matcher baseMatcher = BASE_PATTERN.matcher(result.group(2));            
            
            Set<Long> base = new HashSet();
            while (baseMatcher.find()) {
                base.add(Long.parseLong(baseMatcher.group(1)));
            }
            
            associationRules.add(new AssociationRule(
                associationResult, base, associationAccuracy, baseAccuracy
            ));
        }
        
        reader.close();
        return associationRules;    
    }
    
    private Set<Set<Article>> parseFrequentSets(File frequentSetFile) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(frequentSetFile));
        
        reader.close();
        return null;
    }
    
    @Transactional
    private void saveTransactions(File transactionsFile) {

        List<User> users = (List) em.createQuery("SELECT u FROM User u").getResultList();

        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(transactionsFile));

            for (User user : users) {
                List<Article> articles = (List) em.createQuery("SELECT a FROM Todo t JOIN t.article a WHERE t.user = :user ORDER BY a.id").setParameter("user", user).getResultList();

                if (articles.isEmpty()) continue;
                
                StringBuilder builder = new StringBuilder();
                for (Article a: articles) {
                    if (builder.length() > 0) {
                        builder.append(" ");
                    }

                    builder.append(a.getId());
                }

                writer.append(builder.toString());
                writer.newLine();
            }

            writer.close();
        } catch (IOException e) {
            logger.severe("Could not extract Apriori transactions: " + e);
        }
    }
    
    private boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("win");
    }
}
